﻿using System;
using System.IO;
using System.Collections;
using System.Text;

public class B3OutputStream : B3Output {

	int ptr;
	Stream output;

	public B3OutputStream(Stream output)
	{
		this.output = output;
	}

	public void _Write(int v)
	{
		output.WriteByte((byte)v);
		ptr ++;
	}

	public static void _Write(Stream output, int v)
	{
		output.WriteByte((byte)v);
	}

	public void _Write(B3Types tag)
	{
		int v = (int)tag;
		_Write(v);
	}

	public static void _Write(Stream output, B3Types tag)
	{
		int v = (int)tag;
		_Write(output, v);
	}

	public void _Write(byte[] buf)
	{
		_Write(buf, 0, buf.Length);
	}

	public static void _Write(Stream output, byte[] buf)
	{
		_Write(output, buf, 0, buf.Length);
	}

	public void _Write(byte[] buf, int offset, int count)
	{
		output.Write(buf, offset, count);
		ptr += count;
	}

	public static void _Write(Stream output, byte[] buf, int offset, int count)
	{
		output.Write(buf, offset, count);
	}

	//
	public int Ptr{ get{ return ptr;} }
	// 

	public void WriteBool (bool v)
	{
		WriteBool(output, v);
	}

	public static void WriteBool (Stream output, bool v)
	{
		if (v)
			_Write(output, B3Types.BOOL_TRUE);
		else
			_Write(output, B3Types.BOOL_FALSE);
	}

	public void WriteByte (int v)
	{
		WriteByte(output, v);
	}

	public static void WriteByte (Stream output, int v)
	{
		sbyte b = (sbyte) v;
		if (v == 0) {
			_Write(output, B3Types.BYTE_0);
		} else {
			_Write(output, B3Types.BYTE);
			_Write(output, v);
		}
	}

	public void WriteInt (int v)
	{
		WriteInt(output, v);
	}

	public static void WriteInt (Stream output, int v)
	{
		switch (v) {
		case -1:
			_Write(output, B3Types.INT_N1);
			break;
		case 0:
			_Write(output, B3Types.INT_0);
			break;
		case 1:
			_Write(output, B3Types.INT_1);
			break;
		case 2:
			_Write(output, B3Types.INT_2);
			break;
		case 3:
			_Write(output, B3Types.INT_3);
			break;
		case 4:
			_Write(output, B3Types.INT_4);
			break;
		case 5:
			_Write(output, B3Types.INT_5);
			break;
		case 6:
			_Write(output, B3Types.INT_6);
			break;
		case 7:
			_Write(output, B3Types.INT_7);
			break;
		case 8:
			_Write(output, B3Types.INT_8);
			break;
		case 9:
			_Write(output, B3Types.INT_9);
			break;
		case 10:
			_Write(output, B3Types.INT_10);
			break;
		case 11:
			_Write(output, B3Types.INT_11);
			break;
		case 12:
			_Write(output, B3Types.INT_12);
			break;
		case 13:
			_Write(output, B3Types.INT_13);
			break;
		case 14:
			_Write(output, B3Types.INT_14);
			break;
		case 15:
			_Write(output, B3Types.INT_15);
			break;
		case 16:
			_Write(output, B3Types.INT_16);
			break;
		case 17:
			_Write(output, B3Types.INT_17);
			break;
		case 18:
			_Write(output, B3Types.INT_18);
			break;
		case 19:
			_Write(output, B3Types.INT_19);
			break;
		case 20:
			_Write(output, B3Types.INT_20);
			break;
		case 21:
			_Write(output, B3Types.INT_21);
			break;
		case 22:
			_Write(output, B3Types.INT_22);
			break;
		case 23:
			_Write(output, B3Types.INT_23);
			break;
		case 24:
			_Write(output, B3Types.INT_24);
			break;
		case 25:
			_Write(output, B3Types.INT_25);
			break;
		case 26:
			_Write(output, B3Types.INT_26);
			break;
		case 27:
			_Write(output, B3Types.INT_27);
			break;
		case 28:
			_Write(output, B3Types.INT_28);
			break;
		case 29:
			_Write(output, B3Types.INT_29);
			break;
		case 30:
			_Write(output, B3Types.INT_30);
			break;
		case 31:
			_Write(output, B3Types.INT_31);
			break;
		case 32:
			_Write(output, B3Types.INT_32);
			break;
		default:
			if (B3Constants.b8(v)) {
				_Write(output, B3Types.INT_8B);
				_Write(output, v);
			} else if (B3Constants.b16(v)) {
				_Write(output, B3Types.INT_16B);
				_Write(output, (byte) ((v >> 8) & 0xff));
				_Write(output, (byte) ((v >> 0) & 0xff));
			} else {
				_Write(output, B3Types.INT_32B);
				_Write(output, (byte) ((v >> 24) & 0xff));
				_Write(output, (byte) ((v >> 16) & 0xff));
				_Write(output, (byte) ((v >> 8) & 0xff));
				_Write(output, (byte) ((v >> 0) & 0xff));
			}
			break;
		}
	}

	public void WriteLong (long v)
	{
		WriteLong(output, v);
	}
	public static void WriteLong (Stream output, long v)
	{
		if (v == 0) {
			_Write(output, B3Types.LONG_0);
		} else if (B3Constants.b8(v)) {
			_Write(output, B3Types.LONG_8B);
			_Write(output, (int) v);
		} else if (B3Constants.b16(v)) {
			_Write(output, B3Types.LONG_16B);
			_Write(output, (byte) ((v >> 8) & 0xff));
			_Write(output, (byte) ((v >> 0) & 0xff));
		} else if (B3Constants.b32(v)) {
			_Write(output, B3Types.LONG_32B);
			_Write(output, (byte) ((v >> 24) & 0xff));
			_Write(output, (byte) ((v >> 16) & 0xff));
			_Write(output, (byte) ((v >> 8) & 0xff));
			_Write(output, (byte) ((v >> 0) & 0xff));
		} else {
			_Write(output, B3Types.LONG_64B);
			_Write(output, (byte) ((v >> 56) & 0xff));
			_Write(output, (byte) ((v >> 48) & 0xff));
			_Write(output, (byte) ((v >> 40) & 0xff));
			_Write(output, (byte) ((v >> 32) & 0xff));
			_Write(output, (byte) ((v >> 24) & 0xff));
			_Write(output, (byte) ((v >> 16) & 0xff));
			_Write(output, (byte) ((v >> 8) & 0xff));
			_Write(output, (byte) ((v >> 0) & 0xff));
		}
	}
	public void WriteDouble (double var)
	{
		WriteDouble(output, var);
	}
	public static void WriteDouble (Stream output, double var)
	{
		long v = DoubleToInt64Bits (var);
		if (v == 0) {
			_Write (output, B3Types.DOUBLE_0);
		} else {
			_Write(output, B3Types.DOUBLE_64);
			_Write(output, (byte)((v >> 56) & 0xff));
			_Write(output, (byte)((v >> 48) & 0xff));
			_Write(output, (byte)((v >> 40) & 0xff));
			_Write(output, (byte)((v >> 32) & 0xff));
			_Write(output, (byte)((v >> 24) & 0xff));
			_Write(output, (byte)((v >> 16) & 0xff));
			_Write(output, (byte)((v >> 8) & 0xff));
			_Write(output, (byte)((v >> 0) & 0xff));
		}
	}
	public void WriteUTF (string str)
	{
		WriteUTF(output, str);
	}

	public static void WriteUTF (Stream stream, string str)
	{
		if(str == null){
			_Write(stream, B3Types.NULL);
		} else if (str.Length == 0) {
			_Write(stream, B3Types.UTF8_0);
		} else {
			byte[] buf = Encoding.UTF8.GetBytes (str);
			int size = buf.Length;
			switch (size) {
			case 1:
				_Write(stream, B3Types.UTF8_1);
				break;
			case 2:
				_Write(stream, B3Types.UTF8_2);
				break;
			case 3:
				_Write(stream, B3Types.UTF8_3);
				break;
			case 4:
				_Write(stream, B3Types.UTF8_4);
				break;
			case 5:
				_Write(stream, B3Types.UTF8_5);
				break;
			case 6:
				_Write(stream, B3Types.UTF8_6);
				break;
			case 7:
				_Write(stream, B3Types.UTF8_7);
				break;
			case 8:
				_Write(stream, B3Types.UTF8_8);
				break;
			case 9:
				_Write(stream, B3Types.UTF8_9);
				break;
			case 10:
				_Write(stream, B3Types.UTF8_10);
				break;
			case 11:
				_Write(stream, B3Types.UTF8_11);
				break;
			case 12:
				_Write(stream, B3Types.UTF8_12);
				break;
			case 13:
				_Write(stream, B3Types.UTF8_13);
				break;
			case 14:
				_Write(stream, B3Types.UTF8_14);
				break;
			case 15:
				_Write(stream, B3Types.UTF8_15);
				break;
			case 16:
				_Write(stream, B3Types.UTF8_16);
				break;
			case 17:
				_Write(stream, B3Types.UTF8_17);
				break;
			case 18:
				_Write(stream, B3Types.UTF8_18);
				break;
			case 19:
				_Write(stream, B3Types.UTF8_19);
				break;
			case 20:
				_Write(stream, B3Types.UTF8_20);
				break;
			case 21:
				_Write(stream, B3Types.UTF8_21);
				break;
			case 22:
				_Write(stream, B3Types.UTF8_22);
				break;
			case 23:
				_Write(stream, B3Types.UTF8_23);
				break;
			case 24:
				_Write(stream, B3Types.UTF8_24);
				break;
			case 25:
				_Write(stream, B3Types.UTF8_25);
				break;
			case 26:
				_Write(stream, B3Types.UTF8_26);
				break;
			case 27:
				_Write(stream, B3Types.UTF8_27);
				break;
			case 28:
				_Write(stream, B3Types.UTF8_28);
				break;
			case 29:
				_Write(stream, B3Types.UTF8_29);
				break;
			case 30:
				_Write(stream, B3Types.UTF8_30);
				break;
			case 31:
				_Write(stream, B3Types.UTF8_31);
				break;
			case 32:
				_Write(stream, B3Types.UTF8_32);
				break;
			default:
				_Write(stream, B3Types.UTF8);
				WriteInt(stream, size);
					break;
			}
			_Write(stream, buf);
		}
	}

	public void WriteBytes (byte[] buf)
	{
		WriteBytes(output, buf);
	}
	public static void WriteBytes (Stream output, byte[] buf)
	{
		if (buf == null) {
			_Write(output, B3Types.NULL);
		} else if (buf.Length == 0) {
			_Write(output, B3Types.BYTES_0);
		} else {
			_Write(output, B3Types.BYTES);
			WriteInt(output, buf.Length);
			_Write(output, buf);
		}
	}
	public void WriteList (ArrayList list)
	{
		WriteList(output, list);
	}

	public static void WriteList (Stream output, ArrayList list)
	{
		if (list == null) {
			_Write(output, B3Types.NULL);
		} else if (list.Count == 0) {
			_Write(output, B3Types.LIST_0);
		} else {
			int size = list.Count;
			switch (size) {
			case 1:
				_Write(output, B3Types.LIST_1);
				break;
			case 2:
				_Write(output, B3Types.LIST_2);
				break;
			case 3:
				_Write(output, B3Types.LIST_3);
				break;
			case 4:
				_Write(output, B3Types.LIST_4);
				break;
			case 5:
				_Write(output, B3Types.LIST_5);
				break;
			case 6:
				_Write(output, B3Types.LIST_6);
				break;
			case 7:
				_Write(output, B3Types.LIST_7);
				break;
			case 8:
				_Write(output, B3Types.LIST_8);
				break;
			case 9:
				_Write(output, B3Types.LIST_9);
				break;
			case 10:
				_Write(output, B3Types.LIST_10);
				break;
			case 11:
				_Write(output, B3Types.LIST_11);
				break;
			case 12:
				_Write(output, B3Types.LIST_12);
				break;
			case 13:
				_Write(output, B3Types.LIST_13);
				break;
			case 14:
				_Write(output, B3Types.LIST_14);
				break;
			case 15:
				_Write(output, B3Types.LIST_15);
				break;
			case 16:
				_Write(output, B3Types.LIST_16);
				break;
			default:
				_Write(output, B3Types.LIST);
				WriteInt(output, size);
				break;
			}
			
			foreach (object e in list) {
				WriteObject(output,e);
			}
		}
	}

	public void WriteSet (Hashtable val)
	{
		WriteSet(output, val);
	}
	public static void WriteSet (Stream output, Hashtable val)
	{
		if (val == null)
			_Write(output, B3Types.NULL);
		else if (val.Count == 0)
			_Write(output, B3Types.SET_0);
		else {
			int size = val.Count;
			_Write(output, B3Types.SET);
			WriteInt(output, size);
			foreach (DictionaryEntry e in val) 
			{
				object k = e.Key;
				object v = e.Value;
				WriteObject(output, k);
			}
		}
	}

	public void WriteMap (Hashtable map)
	{
		WriteMap(output, map);
	}
	public static void WriteMap (Stream output, Hashtable map)
	{
		if (map == null) {
			_Write(output, B3Types.NULL);
		} else if (map.Count == 0) {
			_Write(output, B3Types.MAP_0);
		} else {
			int size = map.Count;
			switch (size) {
			case 1:
				_Write(output, B3Types.MAP_1);
				break;
			case 2:
				_Write(output, B3Types.MAP_2);
				break;
			case 3:
				_Write(output, B3Types.MAP_3);
				break;
			case 4:
				_Write(output, B3Types.MAP_4);
				break;
			case 5:
				_Write(output, B3Types.MAP_5);
				break;
			case 6:
				_Write(output, B3Types.MAP_6);
				break;
			case 7:
				_Write(output, B3Types.MAP_7);
				break;
			case 8:
				_Write(output, B3Types.MAP_8);
				break;
			case 9:
				_Write(output, B3Types.MAP_9);
				break;
			case 10:
				_Write(output, B3Types.MAP_10);
				break;
			case 11:
				_Write(output, B3Types.MAP_11);
				break;
			case 12:
				_Write(output, B3Types.MAP_12);
				break;
			case 13:
				_Write(output, B3Types.MAP_13);
				break;
			case 14:
				_Write(output, B3Types.MAP_14);
				break;
			case 15:
				_Write(output, B3Types.MAP_15);
				break;
			case 16:
				_Write(output, B3Types.MAP_16);
				break;
			default:
				_Write(output, B3Types.MAP);
				WriteInt(output, size);
				break;
			}
			
			foreach (DictionaryEntry e in map) {
				object k = e.Key;
				object v = e.Value;
				WriteObject(output, k);
				WriteObject(output, v);
			}
		}
	}
	public void WriteDate (DateTime dat)
	{
		WriteDate(output, dat);
	}
	public static void WriteDate (Stream output, DateTime dat)
	{
		if (dat == null) {
			_Write(output, B3Types.NULL);
		} else {
			long v = DateTimeToJavaDate(dat);
			_Write(output, B3Types.DATE);
			
			_Write(output, (byte) ((v >> 56) & 0xff));
			_Write(output, (byte) ((v >> 48) & 0xff));
			_Write(output, (byte) ((v >> 40) & 0xff));
			_Write(output, (byte) ((v >> 32) & 0xff));
			_Write(output, (byte) ((v >> 24) & 0xff));
			_Write(output, (byte) ((v >> 16) & 0xff));
			_Write(output, (byte) ((v >> 8) & 0xff));
			_Write(output, (byte) ((v >> 0) & 0xff));
		}
	}

	public void WriteIntArray (int[] array)
	{
		WriteIntArray(output, array);
	}

	public static void WriteIntArray (Stream output, int[] array)
	{
		if (array == null)
			_Write(output, B3Types.NULL);
		else if (array.Length == 0)
			_Write(output, B3Types.INT_ARRAY_0);
		else {
			int size = array.Length;
			_Write(output, B3Types.INT_ARRAY);
			WriteInt(output, size);
			foreach (int i in array) {
				WriteInt(output, i);
			}
		}

	}

	public void WriteStrArray (string[] array)
	{
		WriteStrArray(output, array);
	}
	public static void WriteStrArray (Stream output, string[] array)
	{
		if (array == null)
			_Write(output, B3Types.NULL);
		else if (array.Length == 0)
			_Write(output, B3Types.STR_ARRAY_0);
		else {
			int size = array.Length;
			_Write(output, B3Types.STR_ARRAY);
			WriteInt(output, size);
			foreach (string s in array) {
				WriteUTF(output, s);
			}
		}
	}

	public void WriteObject (object val)
	{
		WriteObject(output, val);
	}
	public static void WriteObject (Stream output, object val)
	{
		if (val == null)
			_Write(output, B3Types.NULL);
		// boolean
		else if (val is bool || val is Boolean) {
			bool v = (Boolean) val;
			WriteBool(output, v);
		} else if (val is byte) {
			Byte v = (Byte) val;
			WriteByte(output, v);
		} else if (val is int || val is Int16 || val is Int32) {
			int v = (Int32) val;
			WriteInt(output, v);
		} else if (val is long || val is Int64) {
			long v = (Int64) val;
			WriteLong(output, v);
		} else if (val is double || val is Double) {
			double v = (Double) val;
			WriteDouble(output, v);
		} else if (val is string || val is String) {
			string v = (String) val;
			WriteUTF(output, v);
		} else if (val is byte[]) {
			byte[] v = (byte[]) val;
			WriteBytes(output, v);
		} else if (val is ArrayList) {
			ArrayList v = (ArrayList) val;
			WriteList(output, v);
		} else if (val is Hashtable) {
			Hashtable v = (Hashtable) val;
			WriteMap(output, v);
		} else if (val is DateTime) {
			DateTime v = (DateTime) val;
			WriteDate(output, v);
		} else if (val is int[]) {
			int[] v = (int[]) val;
			WriteIntArray(output, v);
		} else if (val is string[] || val is String[]) {
			string[] v = (String[]) val;
			WriteStrArray(output, v);
		} else {
			throw new IOException("unsupport object type." + val);
		}
	}

	//
	static long DoubleToInt64Bits (double v)
	{
		try {
			return BitConverter.DoubleToInt64Bits (v);
		} catch {
			return 0;
		}
	}

	static long DateTimeToJavaDate (DateTime dat)
	{
		DateTime d1 = new DateTime(1970, 1, 1); 
		DateTime d2 = dat.ToUniversalTime();
		TimeSpan ts = new TimeSpan(d2.Ticks - d1.Ticks);
		return (long)ts.TotalMilliseconds;
	}

	public void Flush()
	{
		output.Flush();
	}

	public void Close()
	{
		output.Close();
	}

}
